/*
 * Copyright (C) 2007 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.example.android.lunarlander;

import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.util.AttributeSet;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.widget.TextView;
import android.widget.Toast;

/**
 * View that draws, takes keystrokes, etc. for a simple LunarLander game.
 * 
 * Has a mode which RUNNING, PAUSED, etc. Has a x, y, dx, dy, ... capturing the
 * current ship physics. All x/y etc. are measured with (0,0) at the lower left.
 * updatePhysics() advances the physics based on realtime. draw() renders the
 * ship, and does an invalidate() to prompt another draw() as soon as possible
 * by the system.
 */
class LunarView extends SurfaceView implements SurfaceHolder.Callback {
	class LunarThread extends Thread {
		// things i need

		public class Location {
			public int x;
			public int y;
		}

		// colors
		private Paint mRedBox;
		private Paint mBlueBox;
		private Paint mGrayBox;
		private Paint mYellowBox;
		private Paint mGrid;

		// game logic/display
		private int mBoxStates[][];
		private RectF mBoard[][];
		private Paint mColors[];

		private RectF m_rectStatusBox;
		private CharSequence mFinalMessages[];

		private int mSize;
		private Location mActiveButton;
		private int mTurn;

		private Bitmap mBackgroundImage;
		private int mCanvasHeight = 1;
		private int mCanvasWidth = 1;
		private int m_iBlockSize;

		private int mDifficulty;

		/** Message handler used by thread to interact with TextView */
		private Handler mHandler;

		/** Used to figure out elapsed time between frames */
		private long mLastTime;
		
		private long mTouchDelay = 400;
		private long mLastTouch = mTouchDelay + 1;

		/** The state of the game. One of READY, RUNNING, PAUSE, LOSE, or WIN */
		private int mMode;

		/** Indicate whether the surface has been created & is ready to draw */
		private boolean mRun = false;

		/** Scratch rect object. */
		private RectF mScratchRect;

		/** Handle to the surface manager object we interact with */
		private SurfaceHolder mSurfaceHolder;

		/** Number of wins in a row. */
		private int mWinsInARow;

		// previous winner for streaks
		private int mLastWinner = 0;

		/*
		 * Difficulty setting constants
		 */
		public static final int DIFFICULTY_EASY = 0;
		public static final int DIFFICULTY_HARD = 1;
		public static final int DIFFICULTY_MEDIUM = 2;

		/*
		 * State-tracking constants
		 */
		public static final int STATE_LOSE = 1;
		public static final int STATE_PAUSE = 2;
		public static final int STATE_READY = 3;
		public static final int STATE_RUNNING = 4;
		public static final int STATE_WIN = 5;

		/*
		 * UI constants (i.e. the speed & fuel bars)
		 */
		public static final int UI_BAR = 100; // width of the bar(s)
		public static final int UI_BAR_HEIGHT = 10; // height of the bar(s)
		private static final String KEY_DIFFICULTY = "mDifficulty";
		private static final String KEY_HEADING = "mHeading";
		private static final String KEY_WINS = "mWinsInARow";

		public LunarThread(SurfaceHolder surfaceHolder, Context context,
				Handler handler) {
			// get handles to some important objects
			mSurfaceHolder = surfaceHolder;
			mHandler = handler;
			mContext = context;

			mRedBox = new Paint();
			mRedBox.setAntiAlias(true);
			mRedBox.setARGB(255, 255, 0, 0);

			mGrayBox = new Paint();
			mGrayBox.setAntiAlias(true);
			mGrayBox.setARGB(255, 219, 219, 219);

			mBlueBox = new Paint();
			mBlueBox.setAntiAlias(true);
			mBlueBox.setARGB(255, 0, 0, 255);

			mYellowBox = new Paint();
			mYellowBox.setAntiAlias(true);
			mYellowBox.setARGB(255, 255, 255, 0);
			mYellowBox.setStrokeWidth(5);

			mGrid = new Paint();
			mGrid.setAntiAlias(true);
			mGrid.setARGB(255, 0, 0, 0);
			mGrid.setStrokeWidth(3);

			mColors = new Paint[4];
			mColors[0] = mGrayBox;
			mColors[1] = mBlueBox;
			mColors[2] = mRedBox;
			mColors[3] = mYellowBox;

			Resources res = context.getResources();
			
			mFinalMessages = new CharSequence[3];
			mFinalMessages[0] = res.getString(R.string.mode_board_full);
			mFinalMessages[1] = res.getString(R.string.mode_win_blue);
			mFinalMessages[2] = res.getString(R.string.mode_win_red);

			mSize = 3;

			mBoxStates = new int[mSize][mSize];
			mBoard = new RectF[mSize][mSize];
						
			// find the block size so the blocks cover iScreenCover percent evenly
			double dScreenCover = 0.7;
			
			int iScreenSizeX = 320;
			int iScreenSizeY = 507;
			
			int iCenterX = iScreenSizeX / 2;
			int iCenterY = iScreenSizeY / 2;
			
			// center and size the blocks to the smaller dimension
			if( iScreenSizeX < iScreenSizeY)
			{
				m_iBlockSize = (int)(((double)iScreenSizeX * dScreenCover) / (double)mSize);				
			}
			else
			{
				m_iBlockSize = (int)(((double)iScreenSizeY * dScreenCover) / (double)mSize);
			}
			
			int iOffsetX = iCenterX - ((mSize * m_iBlockSize)/2);
			int iOffsetY = iCenterY - ((mSize * m_iBlockSize)/2);
			
			for (int i = 0; i < mSize; i++) {
				for (int j = 0; j < mSize; j++) {
					mBoard[i][j] = new RectF((i * m_iBlockSize), (j * m_iBlockSize),
							((i + 1) * m_iBlockSize), ((j + 1) * m_iBlockSize));
					mBoard[i][j].offset(iOffsetX, iOffsetY);
				}
			}
									
			int iStatusBuffer = 5;
			m_rectStatusBox = new RectF(iOffsetX, (m_iBlockSize * mSize) + iOffsetY + iStatusBuffer, (m_iBlockSize * mSize) + iOffsetX, (m_iBlockSize * mSize) + (m_iBlockSize / 2) + iOffsetY + iStatusBuffer);
			
			mActiveButton = new Location();
			mActiveButton.x = -1;
			mActiveButton.y = -1;

			mTurn = 0;

			// we don't need to transform it and it's faster to draw this way
			mBackgroundImage = BitmapFactory.decodeResource(res,
					R.drawable.earthrise);

			mScratchRect = new RectF(0, 0, 0, 0);

			mWinsInARow = 0;
			mDifficulty = DIFFICULTY_MEDIUM;
		}

		/**
		 * Starts the game, setting parameters for the current difficulty.
		 */
		public void doStart() {
			synchronized (mSurfaceHolder) {

				for (int i = 0; i < mSize; i++) {
					for (int j = 0; j < mSize; j++) {
						mBoxStates[i][j] = 0;
					}
				}

				// Adjust difficulty params for EASY/HARD
				if (mDifficulty == DIFFICULTY_EASY) {
				} else if (mDifficulty == DIFFICULTY_HARD) {
				}

				mLastTime = System.currentTimeMillis() + 100;
				setState(STATE_RUNNING);
			}
		}

		/**
		 * Pauses the physics update & animation.
		 */
		public void pause() {
			synchronized (mSurfaceHolder) {
				if (mMode == STATE_RUNNING)
					setState(STATE_PAUSE);
			}
		}

		/**
		 * Restores game state from the indicated Bundle. Typically called when
		 * the Activity is being restored after having been previously
		 * destroyed.
		 * 
		 * @param savedState
		 *            Bundle containing the game state
		 */
		public synchronized void restoreState(Bundle savedState) {
			synchronized (mSurfaceHolder) {
				setState(STATE_PAUSE);
				mDifficulty = savedState.getInt(KEY_DIFFICULTY);
				mWinsInARow = savedState.getInt(KEY_WINS);
			}
		}

		@Override
		public void run() {
			while (mRun) {
				Canvas c = null;
				try {
					c = mSurfaceHolder.lockCanvas(null);
					synchronized (mSurfaceHolder) {
						if (mMode == STATE_RUNNING)
							updatePhysics();
						doDraw(c);
					}
				} finally {
					// do this in a finally so that if an exception is thrown
					// during the above, we don't leave the Surface in an
					// inconsistent state
					if (c != null) {
						mSurfaceHolder.unlockCanvasAndPost(c);
					}
				}
			}
		}

		/**
		 * Dump game state to the provided Bundle. Typically called when the
		 * Activity is being suspended.
		 * 
		 * @return Bundle with this view's state
		 */
		public Bundle saveState(Bundle map) {
			synchronized (mSurfaceHolder) {
				if (map != null) {
					map.putInt(KEY_DIFFICULTY, Integer.valueOf(mDifficulty));
					map.putInt(KEY_WINS, Integer.valueOf(mWinsInARow));
				}
			}
			return map;
		}

		/**
		 * Sets the current difficulty.
		 * 
		 * @param difficulty
		 */
		public void setDifficulty(int difficulty) {
			synchronized (mSurfaceHolder) {
				mDifficulty = difficulty;
			}
		}

		/**
		 * Used to signal the thread whether it should be running or not.
		 * Passing true allows the thread to run; passing false will shut it
		 * down if it's already running. Calling start() after this was most
		 * recently called with false will result in an immediate shutdown.
		 * 
		 * @param b
		 *            true to run, false to shut down
		 */
		public void setRunning(boolean b) {
			mRun = b;
		}

		/**
		 * Sets the game mode. That is, whether we are running, paused, in the
		 * failure state, in the victory state, etc.
		 * 
		 * @see #setState(int, CharSequence)
		 * @param mode
		 *            one of the STATE_* constants
		 */
		public void setState(int mode) {
			synchronized (mSurfaceHolder) {
				setState(mode, null);
			}
		}

		/**
		 * Sets the game mode. That is, whether we are running, paused, in the
		 * failure state, in the victory state, etc.
		 * 
		 * @param mode
		 *            one of the STATE_* constants
		 * @param message
		 *            string to add to screen or null
		 */
		public void setState(int mode, CharSequence message) {
			/*
			 * This method optionally can cause a text message to be displayed
			 * to the user when the mode changes. Since the View that actually
			 * renders that text is part of the main View hierarchy and not
			 * owned by this thread, we can't touch the state of that View.
			 * Instead we use a Message + Handler to relay commands to the main
			 * thread, which updates the user-text View.
			 */
			synchronized (mSurfaceHolder) {
				mMode = mode;

				if (mMode == STATE_RUNNING) {
					Message msg = mHandler.obtainMessage();
					Bundle b = new Bundle();
					b.putString("text", "");
					b.putInt("viz", View.INVISIBLE);
					msg.setData(b);
					mHandler.sendMessage(msg);
				} else {
					Resources res = mContext.getResources();
					CharSequence str = "";
					if (mMode == STATE_READY)
						str = res.getText(R.string.mode_ready);
					else if (mMode == STATE_PAUSE)
						str = res.getText(R.string.mode_pause);
					else if (mMode == STATE_LOSE)
						str = res.getText(R.string.mode_lose);
					else if (mMode == STATE_WIN)
						str = res.getString(R.string.mode_win_prefix)
								+ mWinsInARow + " "
								+ res.getString(R.string.mode_win_suffix);

					if (message != null) {
						str = message + "\n" + str;
					}

					if (mMode == STATE_LOSE)
						mWinsInARow = 0;

					Message msg = mHandler.obtainMessage();
					Bundle b = new Bundle();
					b.putString("text", str.toString());
					b.putInt("viz", View.VISIBLE);
					msg.setData(b);
					mHandler.sendMessage(msg);
				}
			}
		}

		/* Callback invoked when the surface dimensions change. */
		public void setSurfaceSize(int width, int height) {
			// synchronized to make sure these all change atomically
			synchronized (mSurfaceHolder) {
				mCanvasWidth = width;
				mCanvasHeight = height;

				// don't forget to resize the background image
				mBackgroundImage = mBackgroundImage.createScaledBitmap(
						mBackgroundImage, width, height, true);
			}
		}

		/**
		 * Resumes from a pause.
		 */
		public void unpause() {
			// Move the real time clock up to now
			synchronized (mSurfaceHolder) {
				mLastTime = System.currentTimeMillis() + 100;
			}
			setState(STATE_RUNNING);
		}

		/**
		 * Handles a key-down event.
		 * 
		 * @param keyCode
		 *            the key that was pressed
		 * @param msg
		 *            the original event object
		 * @return true
		 */

		boolean AttemptButtonSelect(Location locCurr) {
			if (mBoxStates[locCurr.y][locCurr.x] == 0) {
				mBoxStates[locCurr.y][locCurr.x] = (mTurn + 1);
				mTurn = (mTurn + 1) % 2;
				return true;
			}

			Toast.makeText(
					mContext,
					"This box has already been chosen by a player, please select another box.",
					60).show();

			return false;
		}
		
		boolean CheckTouchForBox(MotionEvent mv) {
			
			for (int i = 0; i < mSize; i++) {
				for (int j = 0; j < mSize; j++) {
					if(mBoard[i][j].contains(mv.getX(), mv.getY()))
					{
							mActiveButton.x = j;
							mActiveButton.y = i;
							
							AttemptButtonSelect(mActiveButton);
					}
				}
			}
			
			return true;
		}
		
		boolean doTouch(MotionEvent mv) {
			if(System.currentTimeMillis() - mLastTouch > mTouchDelay) {
				mLastTouch = System.currentTimeMillis();
				if (mMode == STATE_READY || mMode == STATE_LOSE || mMode == STATE_WIN) {
					// ready-to-start -> start
					doStart();
					return true;
				}
				
				return CheckTouchForBox(mv);
			}
			
			// what is a bad touch?
			return true;
		}

		boolean doKeyDown(int keyCode, KeyEvent msg) {
			synchronized (mSurfaceHolder) {
				boolean okStart = false;
				if (keyCode == KeyEvent.KEYCODE_DPAD_UP)
					okStart = true;
				if (keyCode == KeyEvent.KEYCODE_DPAD_DOWN)
					okStart = true;
				if (keyCode == KeyEvent.KEYCODE_S)
					okStart = true;

				boolean center = (keyCode == KeyEvent.KEYCODE_DPAD_UP);

				if (okStart
						&& (mMode == STATE_READY || mMode == STATE_LOSE || mMode == STATE_WIN)) {
					// when we start with the keyboard, assume keyboard and highlight the center block first
					mActiveButton.x = 1;
					mActiveButton.y = 1;
					
					// ready-to-start -> start
					doStart();
					return true;
				} else if (mMode == STATE_PAUSE && okStart) {
					// paused -> running
					unpause();
					return true;
				} else if (mMode == STATE_RUNNING) {
					// center/space -> fire
					if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER
							|| keyCode == KeyEvent.KEYCODE_SPACE
							|| keyCode == KeyEvent.KEYCODE_ENTER) {
						AttemptButtonSelect(mActiveButton);
						return true;
						// left/q -> left
					} else if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT
							|| keyCode == KeyEvent.KEYCODE_Q) {
						if (mActiveButton.x < 0)
						{
							mActiveButton.x = 0;
						}
						if (mActiveButton.y <= 0)
						{
							mActiveButton.y = mSize - 1;
						}
						else
						{
							mActiveButton.y = mActiveButton.y - 1;
						}
						return true;
						// right/w -> right
					} else if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT
							|| keyCode == KeyEvent.KEYCODE_W) {
						mActiveButton.y = (mActiveButton.y + 1) % mSize;
						if (mActiveButton.x < 0)
						{
							mActiveButton.x = 0;
						}
						return true;
						// up -> pause
					} else if (keyCode == KeyEvent.KEYCODE_DPAD_UP) {
						if (mActiveButton.y < 0)
						{
							mActiveButton.y = 0;
						}
						if (mActiveButton.x <= 0)
						{
							mActiveButton.x = mSize - 1;
						}
						else
						{
							mActiveButton.x = mActiveButton.x - 1;							
						}							
						return true;
					} else if (keyCode == KeyEvent.KEYCODE_DPAD_DOWN) {
						mActiveButton.x = (mActiveButton.x + 1) % mSize;
						if (mActiveButton.y < 0)
						{
							mActiveButton.y = 0;
						}
						return true;
					}
				}

				return false;
			}
		}

		/**
		 * Handles a key-up event.
		 * 
		 * @param keyCode
		 *            the key that was pressed
		 * @param msg
		 *            the original event object
		 * @return true if the key was handled and consumed, or else false
		 */
		boolean doKeyUp(int keyCode, KeyEvent msg) {
			boolean handled = false;

			synchronized (mSurfaceHolder) {
				if (mMode == STATE_RUNNING) {
					if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER
							|| keyCode == KeyEvent.KEYCODE_SPACE) {
						handled = true;
					} else if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT
							|| keyCode == KeyEvent.KEYCODE_Q
							|| keyCode == KeyEvent.KEYCODE_DPAD_RIGHT
							|| keyCode == KeyEvent.KEYCODE_W) {
						handled = true;
					}
				}
			}

			return handled;
		}

		/**
		 * Draws the ship, fuel/speed bars, and background to the provided
		 * Canvas.
		 */
		private void doDraw(Canvas canvas) {
			// Draw the background image. Operations on the Canvas accumulate
			// so this is like clearing the screen.
			canvas.drawBitmap(mBackgroundImage, 0, 0, null);

			// draw the mBoard
			for (int i = 0; i < mSize; i++) {
				for (int j = 0; j < mSize; j++) {
					canvas.drawRect(mBoard[i][j], mColors[mBoxStates[i][j]]);

					canvas.drawLine(mBoard[i][j].left, mBoard[i][j].top,
							mBoard[i][j].right, mBoard[i][j].top, mGrid);
					canvas.drawLine(mBoard[i][j].left, mBoard[i][j].top,
							mBoard[i][j].left, mBoard[i][j].bottom, mGrid);
					canvas.drawLine(mBoard[i][j].left, mBoard[i][j].bottom,
							mBoard[i][j].right, mBoard[i][j].bottom, mGrid);
					canvas.drawLine(mBoard[i][j].right, mBoard[i][j].bottom,
							mBoard[i][j].right, mBoard[i][j].top, mGrid);
				}
			}
			
			// status bar
			canvas.drawRect(m_rectStatusBox, mColors[mTurn + 1]);

			// handle the active button separately

			int j = mActiveButton.x;
			int i = mActiveButton.y;
			
			if( i >= 0 && j >= 0)
			{
				canvas.drawLine(mBoard[i][j].left, mBoard[i][j].top,
						mBoard[i][j].right, mBoard[i][j].top, mYellowBox);
				canvas.drawLine(mBoard[i][j].left, mBoard[i][j].top,
						mBoard[i][j].left, mBoard[i][j].bottom, mYellowBox);
				canvas.drawLine(mBoard[i][j].left, mBoard[i][j].bottom,
						mBoard[i][j].right, mBoard[i][j].bottom, mYellowBox);
				canvas.drawLine(mBoard[i][j].right, mBoard[i][j].bottom,
						mBoard[i][j].right, mBoard[i][j].top, mYellowBox);				
			}
		}

		/**
		 * Figures the lander state (x, y, fuel, ...) based on the passage of
		 * realtime. Does not invalidate(). Called at the start of draw().
		 * Detects the end-of-game and sets the UI to the next state.
		 */

		private int CheckForWinner() {
			if (mBoxStates[0][0] == mBoxStates[0][1]
					&& mBoxStates[0][1] == mBoxStates[0][2]
					&& mBoxStates[0][0] != 0)
				return mBoxStates[0][0];
			if (mBoxStates[1][0] == mBoxStates[1][1]
					&& mBoxStates[1][1] == mBoxStates[1][2]
					&& mBoxStates[1][0] != 0)
				return mBoxStates[1][0];
			if (mBoxStates[2][0] == mBoxStates[2][1]
					&& mBoxStates[2][1] == mBoxStates[2][2]
					&& mBoxStates[2][0] != 0)
				return mBoxStates[2][0];

			if (mBoxStates[0][0] == mBoxStates[1][0]
					&& mBoxStates[1][0] == mBoxStates[2][0]
					&& mBoxStates[0][0] != 0)
				return mBoxStates[0][0];
			if (mBoxStates[0][1] == mBoxStates[1][1]
					&& mBoxStates[1][1] == mBoxStates[2][1]
					&& mBoxStates[0][1] != 0)
				return mBoxStates[0][1];
			if (mBoxStates[0][2] == mBoxStates[1][2]
					&& mBoxStates[1][2] == mBoxStates[2][2]
					&& mBoxStates[0][2] != 0)
				return mBoxStates[0][2];

			if (mBoxStates[0][0] == mBoxStates[1][1]
					&& mBoxStates[1][1] == mBoxStates[2][2]
					&& mBoxStates[0][0] != 0)
				return mBoxStates[0][0];
			if (mBoxStates[0][2] == mBoxStates[1][1]
					&& mBoxStates[1][1] == mBoxStates[2][0]
					&& mBoxStates[0][2] != 0)
				return mBoxStates[0][2];

			return 0;
		}

		private boolean CheckForFullBoard() {
			for (int i = 0; i < 9; i++) {
				if (mBoxStates[i / 3][i % 3] == 0)
					return false;
			}
			return true;
		}

		private void updatePhysics() {
			long now = System.currentTimeMillis();

			// Do nothing if mLastTime is in the future.
			// This allows the game-start to delay the start of the physics
			// by 100ms or whatever.
			if (mLastTime > now)
				return;

			double elapsed = (now - mLastTime) / 1000.0;

			int winner = CheckForWinner();
			if (winner > 0) {
				int result = STATE_WIN;
				setState(result, mFinalMessages[winner]);

				if (winner == mLastWinner || mLastWinner == 0) {
					mLastWinner = winner;
					mWinsInARow++;
				} else {
					mWinsInARow = 1;
				}
			}

			if (CheckForFullBoard()) {
				int result = STATE_LOSE;
				setState(result, mFinalMessages[0]);
			}
		}
	}

	/** Handle to the application context, used to e.g. fetch Drawables. */
	private Context mContext;

	/** Pointer to the text view to display "Paused.." etc. */
	private TextView mStatusText;

	/** The thread that actually draws the animation */
	private LunarThread thread;

	public LunarView(Context context, AttributeSet attrs) {
		super(context, attrs);

		// register our interest in hearing about changes to our surface
		SurfaceHolder holder = getHolder();
		holder.addCallback(this);

		// create thread only; it's started in surfaceCreated()
		thread = new LunarThread(holder, context, new Handler() {
			@Override
			public void handleMessage(Message m) {
				mStatusText.setVisibility(m.getData().getInt("viz"));
				mStatusText.setText(m.getData().getString("text"));
			}
		});

		setFocusable(true); // make sure we get key events
	}

	/**
	 * Fetches the animation thread corresponding to this LunarView.
	 * 
	 * @return the animation thread
	 */
	public LunarThread getThread() {
		return thread;
	}
	
	/**
	 * standard override to get a touch event
	 */

	@Override
	public boolean onTouchEvent(MotionEvent mev) {
		return thread.doTouch(mev);
	}

	/**
	 * Standard override to get key-press events.
	 */
	@Override
	public boolean onKeyDown(int keyCode, KeyEvent msg) {
		return thread.doKeyDown(keyCode, msg);
	}

	/**
	 * Standard override for key-up. We actually care about these, so we can
	 * turn off the engine or stop rotating.
	 */
	@Override
	public boolean onKeyUp(int keyCode, KeyEvent msg) {
		return thread.doKeyUp(keyCode, msg);
	}

	/**
	 * Standard window-focus override. Notice focus lost so we can pause on
	 * focus lost. e.g. user switches to take a call.
	 */
	@Override
	public void onWindowFocusChanged(boolean hasWindowFocus) {
		if (!hasWindowFocus)
			thread.pause();
	}

	/**
	 * Installs a pointer to the text view used for messages.
	 */
	public void setTextView(TextView textView) {
		mStatusText = textView;
	}

	/* Callback invoked when the surface dimensions change. */
	public void surfaceChanged(SurfaceHolder holder, int format, int width,
			int height) {
		thread.setSurfaceSize(width, height);
	}

	/*
	 * Callback invoked when the Surface has been created and is ready to be
	 * used.
	 */
	public void surfaceCreated(SurfaceHolder holder) {
		// start the thread here so that we don't busy-wait in run()
		// waiting for the surface to be created
		thread.setRunning(true);
		thread.start();
	}

	/*
	 * Callback invoked when the Surface has been destroyed and must no longer
	 * be touched. WARNING: after this method returns, the Surface/Canvas must
	 * never be touched again!
	 */
	public void surfaceDestroyed(SurfaceHolder holder) {
		// we have to tell thread to shut down & wait for it to finish, or else
		// it might touch the Surface after we return and explode
		boolean retry = true;
		thread.setRunning(false);
		while (retry) {
			try {
				thread.join();
				retry = false;
			} catch (InterruptedException e) {
			}
		}
	}
}
